﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Epam.PhotoGallery.Interfaces.Providers;
using Epam.PhotoGallery.Interfaces.DataTransferObjects;
using System.Xml;
using System.Configuration;
using System.IO;
using System.Xml.Linq;
using System.Drawing;
using System.Xml.Schema;

namespace Epam.PhotoGallery.Bussines.Providers
{
    public class CommunityDataProvider : ICommunityDataProvider
    {
        #region Constants

        //Common constants
        private const string COMMUNITY_CATEGORIES_STORAGE_PATH_KEY = "communityCategoriesStoragePath";
        private const string NAMESPACE_COMMUNITY_STORAGE = "EpamMogilevCommunity";
        private const string NAMESPACE_PREFIX_COMMUNITY = "epbymog";
        private const string TAG_NAME_IMAGE = "Image";

        //Tag names for categories schema
        private const string TAG_NAME_CATEGORY_CONTAINER = "Categories";
        private const string TAG_NAME_CATEGORY = "Category";

        private const string TAG_NAME_BASE_TOPIC_INFO_CONTAINER = "BaseTopicsInfos";
        private const string TAG_NAME_BASE_TOPIC_INFO = "BaseTopicInfo";

        //Tag names for topic schema
        private const string TAG_NAME_TOPIC = "Topic";
        private const string TAG_NAME_VOTING = "Voting";
        private const string TAG_NAME_VOTING_ELEMENT_CONTAINER = "VotingElements";
        private const string TAG_NAME_VOTING_ELEMENT = "Voting";
        private const string TAG_NAME_COMMENTS_CONTAINER = "Comments";
        private const string TAG_NAME_COMMENT = "Comment";
        private const string TAG_NAME_COMMENT_TEXT = "Text";
        private const string TAG_NAME_COMMENT_IMAGE_CONTAINER = "Images";

        //Attribute names for category tag
        private const string ATTRIBUTE_NAME_CATEGORY_ID = "ID";
        private const string ATTRIBUTE_NAME_CATEGORY_TITLE = "Title";
        private const string ATTRIBUTE_NAME_CATEGORY_DESCRIPTION = "Description";

        //Attribute names for image tag
        private const string ATTRIBUTE_NAME_IMAGE_MIME_TYPE = "mimeType";
        private const string ATTRIBUTE_NAME_IMAGE_IS_URL = "isUrl";

        //Attribute names for base topic information 
        private const string ATTRIBUTE_NAME_BASE_TOPIC_ID = "ID";
        private const string ATTRIBUTE_NAME_BASE_TOPIC_AUTHOR = "Author";
        private const string ATTRIBUTE_NAME_BASE_TOPIC_TITLE = "Title";
        private const string ATTRIBUTE_NAME_BASE_TOPIC_CREATION_DATE = "CreationDate";
        private const string ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE = "Reference";

        //Attribute names for comment tag
        private const string ATTRIBUTE_NAME_COMMENT_ID = "ID";

        //Attribute names for voting element tag
        private const string ATTRIBUTE_NAME_VOTING_ELEMENT_QUANTITY = "QuantityOfVotingElement";
        private const string ATTRIBUTE_NAME_VOTING_ELEMENT_TITLE = "TitleOfVotingElement";

        #endregion

        #region Fields

        private string _comunityStoragePath;

        #endregion

        #region Constructors

        public CommunityDataProvider()
        {
            _comunityStoragePath = ConfigurationManager.AppSettings[COMMUNITY_CATEGORIES_STORAGE_PATH_KEY];
        }

        #endregion

        #region ICommunityDataProvider's methods

        public Topic GetTopic(string categoryID, string topicID)
        {
            Topic currentTopic = new Topic();

            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                reader.ReadToDescendant(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE);

                do
                {
                    if (reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID) == categoryID)
                    {
                        reader.ReadToDescendant(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE);

                        do
                        {
                            if (reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID) == topicID)
                            {
                                currentTopic = GetTopicFromXml(reader);

                                break;
                            }
                        }
                        while (reader.ReadToNextSibling(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE));

                        break;
                    }
                }
                while (reader.ReadToNextSibling(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE));
            }

            return currentTopic;
        }

        public IEnumerable<BaseTopicInfo> GetBaseTopicsInformationFromCategory(string categoryID)
        {
            List<BaseTopicInfo> topicsInfoList = new List<BaseTopicInfo>();

            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                while (reader.ReadToFollowing(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE))
                {
                    if (reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID) == categoryID)
                    {
                        if (reader.ReadToDescendant(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE))
                        {

                            do
                            {
                                BaseTopicInfo baseTopicInfo = new BaseTopicInfo();

                                baseTopicInfo.ID = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID);
                                baseTopicInfo.Title = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_TITLE);
                                baseTopicInfo.Author = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_AUTHOR);
                                baseTopicInfo.CreationDate = DateTime.Parse(reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_CREATION_DATE));

                                topicsInfoList.Add(baseTopicInfo);

                            }
                            while (reader.ReadToNextSibling(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE));
                        }
                    }
                }
            }

            return topicsInfoList;
        }

        public IEnumerable<Category> GetCategories()
        {
            List<Category> categories = new List<Category>();

            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                reader.ReadToDescendant(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE);

                do
                {
                    Category currentCategory = new Category();

                    currentCategory.ID = reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID);
                    currentCategory.Title = reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_TITLE);
                    currentCategory.Description = reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_DESCRIPTION);

                    reader.ReadToDescendant(TAG_NAME_IMAGE, NAMESPACE_COMMUNITY_STORAGE);

                    currentCategory.Image = GetCommunityImage(reader);

                    categories.Add(currentCategory);

                }
                while (reader.ReadToFollowing(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE));
            }

            return categories;
        }

        public Category GetCategory(string categoryID)
        {
            Category category = new Category();

            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                reader.ReadToDescendant(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE);

                do
                {
                    if (reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID) == categoryID)
                    {
                        category.ID = categoryID;
                        category.Title = reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_TITLE);
                        category.Description = reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_DESCRIPTION);

                        reader.ReadToDescendant(TAG_NAME_IMAGE, NAMESPACE_COMMUNITY_STORAGE);

                        category.Image = GetCommunityImage(reader);

                        break;
                    }
                }
                while (reader.ReadToFollowing(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE));
            }

            return category;
        }

        public void DeleteTopic(string topicID, string categoryID)
        {
            XDocument categoriesDoc = XDocument.Load(_comunityStoragePath);
            XNamespace xDocNamespace = NAMESPACE_COMMUNITY_STORAGE;

            var topicCategory = from category in categoriesDoc.Descendants(xDocNamespace + TAG_NAME_CATEGORY)
                                where category.Attribute(ATTRIBUTE_NAME_CATEGORY_ID).Value == categoryID
                                select category;

            var topicPath = from topic in topicCategory.Descendants(xDocNamespace + TAG_NAME_BASE_TOPIC_INFO)
                            where topic.Attribute(ATTRIBUTE_NAME_BASE_TOPIC_ID).Value == topicID
                            select topic.Attribute(ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE);

            XAttribute pathAttribute = topicPath.FirstOrDefault();

            if (pathAttribute != null)
            {
                File.Delete(pathAttribute.Value);
                pathAttribute.Parent.Remove();
                categoriesDoc.Save(_comunityStoragePath);
            }
        }

        public void AddTopic(Topic newTopic, string categoryID)
        {
            AddBasicTopicInfoPart(newTopic, categoryID);

            using (XmlTextWriter writer = new XmlTextWriter(GetNewTopicPath(newTopic.ID), null))
            {
                writer.Formatting = Formatting.Indented;
                writer.Indentation = 3;

                writer.WriteStartDocument();
                writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_TOPIC, NAMESPACE_COMMUNITY_STORAGE);


                if (newTopic.Voting != null)
                {
                    AddVotingPart(writer, newTopic);
                }

                if (newTopic.Comments != null)
                {
                    writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_COMMENTS_CONTAINER, NAMESPACE_COMMUNITY_STORAGE);

                    AddCommentsPart(writer, newTopic.Comments);
                }

                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }

        public void DeleteComment(string commentID, string topicID, string categoryID)
        {
            string topicPath = string.Empty;

            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                while (reader.ReadToFollowing(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE))
                {
                    if (reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID) == topicID)
                    {
                        topicPath = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE);
                        break;
                    }
                }
            }

            XDocument topicDoc = XDocument.Load(topicPath);
            XNamespace xDocNamespace = NAMESPACE_COMMUNITY_STORAGE;

            var comments = from comment in topicDoc.Descendants(xDocNamespace + TAG_NAME_COMMENT)
                           where comment.Attribute(ATTRIBUTE_NAME_COMMENT_ID).Value == commentID
                           select comment;

            comments.Remove();

            topicDoc.Save(topicPath);
        }

        public void AddComment(Comment newComment, string topicID, string categoryID)
        {
            using (XmlReader reader = XmlReader.Create(_comunityStoragePath, GetReaderCommonSettings()))
            {
                reader.ReadToDescendant(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE);

                do
                {
                    if (reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID) == categoryID)
                    {

                        reader.ReadToDescendant(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE);
                        do
                        {
                            if (reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID) == topicID)
                            {
                                string topicPath = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE);
                                XDocument topicDoc = XDocument.Load(topicPath);
                                XNamespace xDocNamespace = NAMESPACE_COMMUNITY_STORAGE;

                                XElement commentsContainer = topicDoc.Root.Element(xDocNamespace + TAG_NAME_COMMENTS_CONTAINER);

                                using (XmlWriter writer = commentsContainer.CreateWriter())
                                {
                                    AddCommentsPart(writer, new List<Comment>() { newComment });
                                }

                                topicDoc.Save(topicPath);
                            }
                        }
                        while (reader.ReadToNextSibling(TAG_NAME_BASE_TOPIC_INFO, NAMESPACE_COMMUNITY_STORAGE));
                    }
                }
                while (reader.ReadToNextSibling(TAG_NAME_CATEGORY, NAMESPACE_COMMUNITY_STORAGE));
            }
        }

        public void AddCategory(Category newCategory)
        {
            XDocument categoriesDoc = XDocument.Load(_comunityStoragePath);
            XNamespace xDocNamespace = NAMESPACE_COMMUNITY_STORAGE;

            XElement category = new XElement(xDocNamespace + TAG_NAME_CATEGORY);

            if (newCategory.Image != null)
            {
                XElement imageTag = new XElement(xDocNamespace + TAG_NAME_IMAGE);

                imageTag.SetAttributeValue(ATTRIBUTE_NAME_IMAGE_IS_URL, newCategory.Image.IsUrlImage);
                
                if (!newCategory.Image.IsUrlImage)
                {
                    imageTag.SetAttributeValue(ATTRIBUTE_NAME_IMAGE_MIME_TYPE, newCategory.Image.ImageData.MimeType);
                    imageTag.Value = Convert.ToBase64String(newCategory.Image.ImageData.Content);
                }
                else
                {
                    imageTag.Value = newCategory.Image.Path;
                }

                category.Add(imageTag);
            }

            category.SetAttributeValue(ATTRIBUTE_NAME_CATEGORY_ID, newCategory.ID);
            category.SetAttributeValue(ATTRIBUTE_NAME_CATEGORY_TITLE, newCategory.Title);
            category.SetAttributeValue(ATTRIBUTE_NAME_CATEGORY_DESCRIPTION, newCategory.Description);

            XElement categoriesContainer = categoriesDoc.Element(xDocNamespace + TAG_NAME_CATEGORY_CONTAINER);

            categoriesContainer.Add(category);

            categoriesDoc.Save(_comunityStoragePath);
        }

        #endregion

        #region Private methods

        private Topic GetTopicFromXml(XmlReader reader)
        {
            Topic currentTopic = new Topic();

            currentTopic.Author = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_AUTHOR);
            currentTopic.CreationDate = DateTime.Parse(reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_CREATION_DATE));
            currentTopic.ID = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID);
            currentTopic.Title = reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_TITLE);

            using (XmlReader topicReader = XmlReader.Create(reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE), GetReaderCommonSettings()))
            {
                while (topicReader.Read())
                {
                    if (IsVotingElement(topicReader))
                    {
                        VotingElement currentVotingElement = new VotingElement();

                        currentVotingElement.Quantity = int.Parse(topicReader.GetAttribute(ATTRIBUTE_NAME_VOTING_ELEMENT_QUANTITY));
                        currentVotingElement.Text = topicReader.GetAttribute(ATTRIBUTE_NAME_VOTING_ELEMENT_TITLE);
                        currentTopic.Voting.Items.Add(currentVotingElement);
                    }

                    else if (IsComment(topicReader))
                    {
                        Comment currentComment = new Comment();

                        currentComment.ID = topicReader.GetAttribute(ATTRIBUTE_NAME_COMMENT_ID);

                        topicReader.ReadToDescendant(TAG_NAME_COMMENT_TEXT, NAMESPACE_COMMUNITY_STORAGE);

                        currentComment.Text = topicReader.ReadElementContentAsString();

                        reader.ReadToDescendant(TAG_NAME_IMAGE, NAMESPACE_COMMUNITY_STORAGE);

                        do
                        {
                            CommunityImage commentImage = GetCommunityImage(reader);

                            currentComment.Images.Add(commentImage);
                        }
                        while (reader.ReadToNextSibling(TAG_NAME_IMAGE, NAMESPACE_COMMUNITY_STORAGE));

                        currentTopic.Comments.Add(currentComment);
                    }
                }
            }
            return currentTopic;
        }

        private XmlReaderSettings GetReaderCommonSettings()
        {
            XmlReaderSettings settings = new XmlReaderSettings();

            settings.IgnoreComments = true;
            settings.CloseInput = true;
            //settings.Schemas.Add(new XmlSchema()); Add correct schema for settings

            return settings;
        }

        private XmlWriterSettings GetWriterCommonSettings()
        {
            XmlWriterSettings settings = new XmlWriterSettings();

            settings.Indent = true;

            return settings;
        }

        private void AddBasicTopicInfoPart(BaseTopicInfo baseTopicInfo, string categoryID)
        {
            XDocument categoriesDoc = XDocument.Load(_comunityStoragePath);
            XNamespace xDocNamespace = NAMESPACE_COMMUNITY_STORAGE;
            XElement category = categoriesDoc.Root.Descendants(xDocNamespace + TAG_NAME_CATEGORY).FirstOrDefault(
                element => element.Attribute(ATTRIBUTE_NAME_CATEGORY_ID).Value == categoryID.ToString());

            XElement topicInfo = new XElement(xDocNamespace + TAG_NAME_BASE_TOPIC_INFO);

            topicInfo.SetAttributeValue(ATTRIBUTE_NAME_BASE_TOPIC_ID, baseTopicInfo.ID.ToString());
            topicInfo.SetAttributeValue(ATTRIBUTE_NAME_BASE_TOPIC_TITLE, baseTopicInfo.Title);
            topicInfo.SetAttributeValue(ATTRIBUTE_NAME_BASE_TOPIC_AUTHOR, baseTopicInfo.Author);
            topicInfo.SetAttributeValue(ATTRIBUTE_NAME_BASE_TOPIC_CREATION_DATE, baseTopicInfo.CreationDate.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture));
            topicInfo.SetAttributeValue(ATTRIBUTE_NAME_BASE_TOPIC_REFERENCE, GetNewTopicPath(baseTopicInfo.ID));

            XElement topicInfoContainer = category.Element(xDocNamespace + TAG_NAME_BASE_TOPIC_INFO_CONTAINER);

            topicInfoContainer.Add(topicInfo);

            categoriesDoc.Save(_comunityStoragePath);
        }
        private void AddVotingPart(XmlWriter writer, Topic newTopic)
        {
            writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_VOTING, NAMESPACE_COMMUNITY_STORAGE);
            writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_VOTING_ELEMENT_CONTAINER, NAMESPACE_COMMUNITY_STORAGE);

            foreach (var votingItem in newTopic.Voting.Items)
            {
                writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_VOTING_ELEMENT, NAMESPACE_COMMUNITY_STORAGE);
                writer.WriteAttributeString(ATTRIBUTE_NAME_VOTING_ELEMENT_QUANTITY, votingItem.Quantity.ToString());
                writer.WriteAttributeString(ATTRIBUTE_NAME_VOTING_ELEMENT_TITLE, votingItem.Text);
                writer.WriteEndElement();
            }

            writer.WriteEndElement();
            writer.WriteEndElement();
        }
        private void AddCommentsPart(XmlWriter writer, IEnumerable<Comment> comments)
        {
            foreach (var comment in comments)
            {
                writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_COMMENT, NAMESPACE_COMMUNITY_STORAGE);
                writer.WriteAttributeString(ATTRIBUTE_NAME_COMMENT_ID, comment.ID.ToString());
                writer.WriteElementString(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_COMMENT_TEXT, NAMESPACE_COMMUNITY_STORAGE, comment.Text);

                writer.WriteStartElement(NAMESPACE_PREFIX_COMMUNITY, TAG_NAME_COMMENT_IMAGE_CONTAINER, NAMESPACE_COMMUNITY_STORAGE);

                foreach (var image in comment.Images)
                {
                    writer.WriteStartElement(TAG_NAME_IMAGE, NAMESPACE_COMMUNITY_STORAGE);
                    writer.WriteAttributeString(ATTRIBUTE_NAME_IMAGE_IS_URL, image.IsUrlImage.ToString());

                    if (!image.IsUrlImage)
                    {
                        writer.WriteAttributeString(ATTRIBUTE_NAME_IMAGE_MIME_TYPE, image.ImageData.MimeType);
                        writer.WriteString(Convert.ToBase64String(image.ImageData.Content));
                    }
                    else
                    {
                        writer.WriteString(image.Path);
                    }

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }
        }

        private bool IsRightTopic(XmlReader reader, string topicID)
        {
            return reader.NodeType == XmlNodeType.Element
                && reader.Name == TAG_NAME_BASE_TOPIC_INFO
                && reader.GetAttribute(ATTRIBUTE_NAME_BASE_TOPIC_ID) == topicID;
        }

        private bool IsRightCategory(XmlReader reader, string categoryID)
        {
            return reader.NodeType == XmlNodeType.Element
                && reader.Name == TAG_NAME_CATEGORY
                && reader.GetAttribute(ATTRIBUTE_NAME_CATEGORY_ID) == categoryID;
        }

        private bool IsVotingElement(XmlReader topicReader)
        {
            return topicReader.NodeType == XmlNodeType.Element
                && topicReader.LocalName == TAG_NAME_VOTING_ELEMENT
                && topicReader.NamespaceURI == NAMESPACE_COMMUNITY_STORAGE;
        }

        private bool IsComment(XmlReader topicReader)
        {
            return topicReader.NodeType == XmlNodeType.Element
                && topicReader.LocalName == TAG_NAME_COMMENT
                && topicReader.NamespaceURI == NAMESPACE_COMMUNITY_STORAGE;
        }

        private CommunityImage GetCommunityImage(XmlReader reader)
        {
            CommunityImage image;

            if (reader.LocalName == TAG_NAME_IMAGE)
            {
                bool isUrlAttribute=bool.Parse(reader.GetAttribute(ATTRIBUTE_NAME_IMAGE_IS_URL));

                if (!isUrlAttribute)
                {
                    image = new CommunityImage(ImageStorageType.Database);

                    image.ImageData.MimeType = reader.GetAttribute(ATTRIBUTE_NAME_IMAGE_MIME_TYPE);
                    image.ImageData.Content = Convert.FromBase64String(reader.ReadElementContentAsString());
                }
                else
                {
                    image = new CommunityImage(ImageStorageType.Url);

                    image.Path = reader.ReadElementContentAsString();
                }

            }

            else
            {
                image = null;
            }

            return image;
        }


        private string GetNewTopicPath(string topicID)
        {
            return topicID + ".xml";
        }

        #endregion
    }
}
